import Designer from '@/index';
import Stage from '.';
import { dist, hotkey, calcRectDist, getNodesMinEnclosingRect, nodesToMap, toFixed } from '@/utils';
import { onNodeDragStart, onRegionStart } from './evnet';

/**
 * 处理 hover 事件
 * @param {MouseEvent} e
 * @this {Stage}
 */
export function handleHover(e) {
  const { scrollbar, selectable, dragging, scrolling, nodeDragging, regioning, edging, transformer } = this;
  const { rotating, resizing } = transformer;
  // 重置 hover 节点、锚点、节点连线、滚动条
  this.hoveringNode = this.hoveringAnchor = this.hoveringEdge = this.hoveringScrollbar = null;
  // 获取当前 hover 滚动条
  if (scrollbar) {
    const { horizontal, vertical } = computeScrollbar.call(this);
    if (horizontal?.hovering) this.hoveringScrollbar = horizontal;
    else if (vertical?.hovering) this.hoveringScrollbar = vertical;
    if (this.hoveringScrollbar) return;
  }
  if (!selectable) return;
  // 画布相对坐标溢出 不触发 hover
  if (this.overflowCoord.isOverflow) return;
  // 画布拖动中状态 或 滚动条操作状态 或 节点拖动中状态 或 框选中状态 或 转换器旋转，调整大小状态 不可触发 hover
  if (dragging || scrolling || nodeDragging || regioning || rotating || resizing) return;
  // 转换器事件
  const transformAction = transformer.pointAction(this.gridCoord);
  // 获取当前 hover 节点（最高优先级）
  const getHoveringNode = () => {
    // 有转换器事件 且 shift键未按下
    if (transformAction && !e?.shiftKey) return null;
    // 节点连线中状态
    if (edging) return null;
    const { hoverNode, selectedNodes, draggableNodes, depthOperation, lastTopGroup } = this;
    let targetNode = hoverNode;
    // 鼠标处于画布节点
    if (targetNode === this) return null;
    // 画板节点 取子节点
    if (['Board'].includes(targetNode.className)) {
      // 目标节点为画板节点
      if (targetNode === targetNode.hoverNode) return null;
      targetNode = targetNode.hoverNode;
    }
    // 只选中了编组节点 且鼠标位于该编组节点 取该编组内子节点
    if (selectedNodes.length === 1 && selectedNodes[0].className === 'Group' && targetNode === selectedNodes[0]) {
      targetNode = targetNode.hoverNode;
    }
    // 可深度操作子节点 且 目标节点为上一次最顶层父级编组
    if (depthOperation && lastTopGroup === targetNode) return null;
    // 为已选中的节点
    if (selectedNodes.includes(targetNode)) return targetNode;
    // 为当前可拖动节点
    if (draggableNodes.includes(targetNode)) return null;
    return targetNode;
  };
  // 获取当前 hover 节点连线（高优先级）
  const getHoveringNodeEdge = () => {
    // 有转换器事件 且 shift键未按下
    if (transformAction && !e?.shiftKey) return null;
    // 已有 hover 节点
    if (this.hoveringNode) return null;
    // 节点连线中状态
    if (edging) return null;
    return this._edges.findLast((i) => i.isPointIn) || null;
  };
  // 获取当前 hover 锚点（低优先级）
  const getHoveringAnchor = () => {
    // 有转换器事件
    if (transformAction) return null;
    // 已有 hover 节点连线
    if (this.hoveringEdge) return null;
    const hoverAnchors = this.allVisibleNodesAnchors.filter((i) => i.isPointIn);
    const { gridCoord } = this;
    hoverAnchors.sort((a, b) => dist(a.p, gridCoord) - dist(b.p, gridCoord));
    return hoverAnchors[0] || null;
  };
  // 更新 hover 节点（最高优先级）
  this.hoveringNode = getHoveringNode();
  // 更新 hover 节点连线（高优先级）
  this.hoveringEdge = getHoveringNodeEdge();
  // 更新 hover 锚点（低优先级）
  this.hoveringAnchor = getHoveringAnchor();
}

/**
 * 处理选择事件
 * @param {MouseEvent} e
 * @this {Stage}
 */
export function handleSelect(e) {
  const { hoveringEdge } = this;
  let targetNode = this.hoverNode;
  // 画板节点 取子节点
  if (['Board'].includes(targetNode.className)) targetNode = targetNode.hoverNode;
  // 编组节点 深度取子节点
  if (['Group'].includes(targetNode.className)) {
    while (targetNode.hoverNode && targetNode !== targetNode.hoverNode) targetNode = targetNode.hoverNode;
  }
  // shift键按下 触发特殊的选择方式
  if (e?.shiftKey) {
    // 取消可深度操作节点状态
    this.depthOperation = false;
    let specialNode;
    // 目标节点为画布节点 在所有画板子节点中寻找鼠标悬停的节点 => 多选节点
    if (targetNode === this) {
      const boradList = this.find('Board').filter((i) => i.clipShow);
      boradList.some((borad) => {
        return (specialNode = borad.visibleNodes.findLast((node) => node.isPointIn()));
      });
    }
    // 当前可拖动节点 不包含 目标节点最顶层父级编组或目标节点 => 多选节点
    else if (!this.draggableNodes.includes(targetNode.topGroup || targetNode)) specialNode = targetNode;
    if (specialNode && !this.selectedNodes.includes(specialNode)) {
      this.selectedNodes.push(specialNode);
      this.emit('select', this.selectedNodes, 'select');
    }
    // 有 hover 节点连线 => 多选节点连线
    if (hoveringEdge && !this.selectedEdges.includes(hoveringEdge.id)) {
      this.selectedEdges.push(hoveringEdge.id);
      this.emit('edgeSelect', this.findEdge(this.selectedEdges), 'select');
    }
  }
  // 有 hover 节点连线 选择单个节点连线 并 清空已选节点
  else if (hoveringEdge) {
    this.selectedEdges = [hoveringEdge.id];
    this.emit('edgeSelect', this.findEdge(this.selectedEdges), 'select');
    this.selectedNodes = [];
    this.emit('select', this.selectedNodes, 'select');
  }
  // 画布及画板节点时 清除框选 清空已选节点和节点连线 并 触发框选
  else if (['Stage', 'Board'].includes(targetNode.className)) {
    this.selectedNodes = [];
    this.emit('select', this.selectedNodes, 'select');
    this.selectedEdges = [];
    this.emit('edgeSelect', this.findEdge(this.selectedEdges), 'select');
    // @ts-ignore 将待框选节点挂载到 selectedNodes 对象上
    this.selectedNodes.waitRegionNodes = targetNode.nodes;
    // 触发框选
    onRegionStart.call(this, e);
  }
  // 否则 选择单个节点 并 清空已选节点连线
  else {
    this.selectedNodes = [targetNode];
    this.emit('select', this.selectedNodes, 'select');
    this.selectedEdges = [];
    this.emit('edgeSelect', this.findEdge(this.selectedEdges), 'select');
  }
  // 更新 上一次最顶层父级编组 及 是否可深度操作节点状态
  const { draggableNodes } = this;
  if (!draggableNodes.length || draggableNodes.length > 1) (this.lastTopGroup = null), (this.depthOperation = false);
  else {
    const node = draggableNodes[0];
    const topGroup = node.topGroup ?? (node.className === 'Group' ? node : null);
    if (this.lastTopGroup !== topGroup) (this.lastTopGroup = topGroup), (this.depthOperation = false);
  }
  // 将当前可拖动节点更新到转换器
  this.transformer.setNodes(this.draggableNodes);
  // 记录画布历史状态
  this._handleRecord();
  // 存在可拖动的节点 触发节点拖动
  if (this.draggableNodes.length) onNodeDragStart.call(this, e);
}

/**
 * 更新鼠标指针
 * @param {MouseEvent} e
 * @this {Stage}
 */
export function updateCursor(e) {
  const { draggable, selectable, dragging, scrolling, nodeDragging, regioning, edging, transformer, gridCoord } = this;
  const { rotating, resizing } = transformer;
  let isDefault = false;
  // 转换器事件
  const transformAction = transformer.pointAction(gridCoord);
  // 滚动条操作
  if (this.hoveringScrollbar) isDefault = true;
  // 可拖动
  else if (
    // 画布可拖动 且
    draggable &&
    // 非拖动中状态 且
    !dragging &&
    // 不可选择节点 或（空格键按下 且 非 滚动条操作状态 或 节点拖动中状态 或 框选中状态 或 节点连线中状态 或 转换器旋转，调整大小状态）
    (!selectable ||
      (this.pressedKeys.has(' ') && !(scrolling || nodeDragging || regioning || edging || rotating || resizing)))
  ) {
    this.setCursor('grab');
  }
  // 拖动中
  else if (dragging) this.setCursor('grabbing');
  // 节点拖动中
  else if (nodeDragging) this.setCursor('move');
  // shift键按下
  else if (e?.shiftKey) isDefault = true;
  // 转换器事件 设置转换器控件指针样式
  else if (transformAction) transformer.setCursor(transformAction);
  // 锚点 更新锚点指针样式
  else if (!this.hoveringEdge && this.hoveringAnchor) {
    const { edgeable, tempAnchor, hoveringAnchor, tempEdge } = this;
    // 禁用节点连线功能
    if (!edgeable) {
      this.setCursor('not-allowed');
      return;
    }
    let disabled = false;
    // 开始锚点
    if (!tempAnchor && hoveringAnchor) {
      const edge = { source: hoveringAnchor.node.id, sourceAnchor: hoveringAnchor.id };
      disabled = this._edgePrecheck('start', edge) === false;
    }
    // 锚点连线
    else if (tempEdge) {
      disabled = !tempEdge.canAdd;
    }
    this.setCursor(disabled ? 'not-allowed' : 'crosshair');
  } else isDefault = true;
  // 默认
  if (isDefault) {
    this.setTitle();
    this.setCursor();
  }
}

/**
 * 处理复制事件
 * @this {Stage}
 */
export async function handleCopy(e) {
  // 快捷键行为预检
  if ((await this.hotkeyPrecheck('copy')) === false) return;
  const { selectedNodes } = this;
  // 收集所有文本节点
  const textArr = selectedNodes.reduce((arr, node) => {
    if (node.className === 'Text') arr.push(node.text);
    return arr;
  }, []);
  e.clipboardData.setData('text/plain', textArr.join('\n'));
  // 收集要复制的节点 忽略画板节点
  const nodes = Designer.toJSON(selectedNodes.filter((i) => i.className !== 'Board'));
  e.clipboardData.setData('designer-nodes', nodes);
  // 发布快捷键事件
  this.emit('hotkey', 'copy');
}

/**
 * 处理剪切事件
 * @this {Stage}
 */
export async function handleCut(e) {
  // 快捷键行为预检
  if ((await this.hotkeyPrecheck('cut')) === false) return;
  // 触发复制
  handleCopy.call(this, e);
  const { selectedNodes } = this;
  // 删除已选节点
  selectedNodes.forEach((node) => node.remove(true));
  if (selectedNodes.length) this.setSelectedNodes([]);
  // 发布快捷键事件
  this.emit('hotkey', 'cut');
}

/**
 * 处理粘贴事件
 * @this {Stage}
 */
export async function handlePaste(e) {
  if (!e.clipboardData.types.includes('designer-nodes')) return;
  let nodes = e.clipboardData.getData('designer-nodes');
  if (nodes) nodes = JSON.parse(nodes);
  if (!nodes) return;
  // 快捷键行为预检
  if ((await this.hotkeyPrecheck('paste', nodes)) === false) return;
  // 清空已选节点
  this.selectedNodes.length = 0;
  nodes.forEach((node) => {
    // 寻找父级节点
    const parent = this.findOne(`#${node.parent}`) || this;
    // 克隆节点
    const nodeInstance = Designer.clone(node);
    // 添加到父级节点
    parent.add(nodeInstance, true);
    // 添加选中节点
    this.selectedNodes.push(nodeInstance);
  });
  // 更新已选节点
  this.setSelectedNodes(this.selectedNodes);
  // 发布快捷键事件
  this.emit('hotkey', 'paste');
}

/**
 * 触发快捷键行为
 * @this {Stage}
 */
export async function hotkeyAction(action) {
  const { pressedKeys } = this;
  // 快捷键行为预检
  if ((await this.hotkeyPrecheck(action)) === false) return;
  // 快捷键行为
  const ACTION = {
    // 撤销
    undo: () => this.undo(),
    // 重做
    redo: () => this.redo(),
    // 删除
    delete: () => {
      const { selectedNodes, selectedEdges } = this;
      // 删除已选节点
      selectedNodes.forEach((node) => node.remove(true));
      if (selectedNodes.length) this.setSelectedNodes([]);
      // 删除已选节点连线
      this.removeEdge(selectedEdges, true);
      if (selectedEdges.length) this.setSelectedEdges([]);
    },
    // 全选
    selectAll: () => this.setSelectedNodes((this.selectedNodes[0]?.topBoard || this).nodes),
    // 放大
    increaseScale: () => this.increaseScale(),
    // 缩小
    decreaseScale: () => this.decreaseScale(),
    // 节点移动
    move: () => {
      const offset = { x: 0, y: 0 };
      if (pressedKeys.has('ArrowUp')) offset.y -= 1;
      if (pressedKeys.has('ArrowDown')) offset.y += 1;
      if (pressedKeys.has('ArrowLeft')) offset.x -= 1;
      if (pressedKeys.has('ArrowRight')) offset.x += 1;
      if ((offset.x || offset.y) && !this.nodeDragging) this.moveDraggableNodes(offset);
    },
  };
  if (!ACTION[action]) return;
  // 执行快捷键行为
  ACTION[action]();
  // 发布快捷键事件
  this.emit('hotkey', action);
}
/**
 * 处理快捷键事件
 * @this {Stage}
 */
export async function handleHotkey(e) {
  // 获取快捷键行为
  let action = '';
  // 撤销
  if (hotkey(e, ['ctrl', 'z'])) action = 'undo';
  // 重做
  else if (hotkey(e, ['ctrl', 'y']) || hotkey(e, ['ctrl', 'shift', 'z'])) action = 'redo';
  // 删除
  else if (hotkey(e, ['Delete']) || hotkey(e, ['Backspace'])) action = 'delete';
  // 全选
  else if (hotkey(e, ['ctrl', 'a'])) action = 'selectAll';
  // 放大
  else if (hotkey(e, ['ctrl', '+']) || hotkey(e, ['ctrl', '='])) action = 'increaseScale';
  // 缩小
  else if (hotkey(e, ['ctrl', '-'])) action = 'decreaseScale';
  // 节点移动
  else if (hotkey(e, ['ArrowUp']) || hotkey(e, ['ArrowDown']) || hotkey(e, ['ArrowLeft']) || hotkey(e, ['ArrowRight']))
    action = 'move';
  if (!action) return;
  // 触发快捷键行为
  hotkeyAction.call(this, action);
}

/**
 * 生成辅助线
 * @this {Stage}
 */
export function generateGuides() {
  if (!this.guidesable) return;
  const { scale, guides, draggableNodes } = this;
  // 阈值
  const threshold = 3 / scale;
  // 是否自动校正
  const correct = true;
  // 辅助线存在标记
  const exist = {};
  // 清空辅助线
  guides.splice(0);
  // 获取可拖动节点相对网格坐标的最小外接矩形
  const dragRect = getNodesMinEnclosingRect(draggableNodes, true);
  if (!dragRect) return;
  // 生成 x 轴辅助线
  const generateX = (rect, dragKey) => {
    let key;
    if (Math.abs(dragRect[dragKey] - rect['centerY']) <= threshold) key = 'centerY';
    else if (Math.abs(dragRect[dragKey] - rect['top']) <= threshold) key = 'top';
    else if (Math.abs(dragRect[dragKey] - rect['bottom']) <= threshold) key = 'bottom';
    if (!key) return false;
    if (correct) {
      const offset = { x: 0, y: -(dragRect[dragKey] - rect[key]) };
      this.moveDraggableNodes(offset, true);
      (dragRect.top += offset.y), (dragRect.bottom += offset.y), (dragRect.centerY += offset.y);
    }
    const y = rect[key];
    const lx = Math.min(dragRect.left, rect.left);
    const rx = Math.max(dragRect.right, rect.right);
    guides.push({ type: 'line', o: { x: lx, y: y }, d: { x: rx, y: y } });
    const dist = toFixed(rx - lx - dragRect.width - rect.width, 0);
    if (dist > threshold) {
      const x = (Math.max(dragRect.left, rect.left) + Math.min(dragRect.right, rect.right)) / 2;
      guides.push({ type: 'label', x, y, dist });
    }
    return true;
  };
  // 生成 y 轴辅助线
  const generateY = (rect, dragKey) => {
    let key;
    if (Math.abs(dragRect[dragKey] - rect['centerX']) <= threshold) key = 'centerX';
    else if (Math.abs(dragRect[dragKey] - rect['left']) <= threshold) key = 'left';
    else if (Math.abs(dragRect[dragKey] - rect['right']) <= threshold) key = 'right';
    if (!key) return false;
    if (correct) {
      const offset = { x: -(dragRect[dragKey] - rect[key]), y: 0 };
      this.moveDraggableNodes(offset, true);
      (dragRect.left += offset.x), (dragRect.right += offset.x), (dragRect.centerX += offset.x);
    }
    const x = rect[key];
    const ty = Math.min(dragRect.top, rect.top);
    const by = Math.max(dragRect.bottom, rect.bottom);
    guides.push({ type: 'line', o: { x, y: ty }, d: { x, y: by } });
    const dist = toFixed(by - ty - dragRect.height - rect.height, 0);
    if (dist > threshold) {
      const y = (Math.max(dragRect.top, rect.top) + Math.min(dragRect.bottom, rect.bottom)) / 2;
      guides.push({ type: 'label', x, y, dist });
    }
    return true;
  };
  // 生成辅助线
  const generateGuide = (node) => {
    if (exist['y'] && exist['x']) return;
    const rect = guideNodesInfoMap[node.id].gridMinEnclosingRect;
    // 拖动矩形 中心点X 与节点：左边、中心点X、右边
    if (!exist['y'] && generateY(rect, 'centerX')) (exist['y'] = true), guides.push({ type: 'node', node });
    // 拖动矩形 左边 与节点：左边、中心点X、右边
    else if (!exist['y'] && generateY(rect, 'left')) (exist['y'] = true), guides.push({ type: 'node', node });
    // 拖动矩形 右边 与节点：左边、中心点X、右边
    else if (!exist['y'] && generateY(rect, 'right')) (exist['y'] = true), guides.push({ type: 'node', node });
    // 拖动矩形 中心点Y 与节点 上边、中心点Y、下边
    if (!exist['x'] && generateX(rect, 'centerY')) (exist['x'] = true), guides.push({ type: 'node', node });
    // 拖动矩形 上边 与节点 上边、中心点Y、下边
    else if (!exist['x'] && generateX(rect, 'top')) (exist['x'] = true), guides.push({ type: 'node', node });
    // 拖动矩形 下边 与节点 上边、中心点Y、下边
    else if (!exist['x'] && generateX(rect, 'bottom')) (exist['x'] = true), guides.push({ type: 'node', node });
  };
  // 目标节点：可拖动节点中 可见兄弟节点为0时：目标节点使用非画布且可见兄弟节点不为0的父级节点
  const targetNodes = draggableNodes.map((node) => {
    while (node.parent.className !== 'Stage' && !node.siblings.filter((i) => i.visible).length) node = node.parent;
    return node;
  });
  // 目标节点的映射对象
  const targetNodesMap = nodesToMap(targetNodes);
  // 所有父级节点
  const parents = [...new Set(targetNodes.reduce((parents, node) => (parents.push(node.parent), parents), []))];
  // 需要辅助的目标节点：所有目标节点的可见兄弟节点 及 父级节点
  const guideNodes = parents.reduce((nodes, parent) => {
    nodes.push(...parent.nodes.filter((i) => i.visible && !targetNodesMap[i.id]));
    return nodes;
  }, []);
  // 带上父级节点，过滤出 非画布节点 且 子节点数大于1的父级节点
  guideNodes.push(...parents.filter((i) => i !== this && i.nodes.length > 1));
  // 需要辅助的目标节点信息映射对象：收集辅助节点相对网格坐标的最小外接矩形 及 与当前拖动矩形距离
  const guideNodesInfoMap = guideNodes.reduce((map, node) => {
    const { gridMinEnclosingRect } = node;
    map[node.id] = { gridMinEnclosingRect, dist: calcRectDist(dragRect, gridMinEnclosingRect) };
    return map;
  }, {});
  // 辅助节点按与当前拖动矩形距离从小到大排序
  guideNodes.sort((a, b) => guideNodesInfoMap[a.id].dist - guideNodesInfoMap[b.id].dist);
  // 生成与辅助节点的辅助线
  guideNodes.forEach((node) => generateGuide(node));
}

/**
 * 计算滚动条
 * @this {Stage}
 */
export function computeScrollbar() {
  const c = (num) => this.calc(num, { adaptScale: false }); // 只适应画布像素比
  const thickness = c(6); // 滚动条厚度
  const padding = c(5); // 滚动条边距
  const radius = c(4); // 圆角半径
  const lineWidth = c(1); // 描边宽度
  const { ctx, scale, canvasWidth, canvasHeight, minEnclosingRect, offsetCoord } = this;
  // 不存在画布实际绘制区域，不需要滚动条
  if (!minEnclosingRect) return { horizontal: null, vertical: null };
  const { left, right, top, bottom } = minEnclosingRect; // 画布实际绘制区域信息
  // 内容区域（转为画布相对坐标）
  const content = {
    points: [this.getOffsetCoord({ x: left, y: top }), this.getOffsetCoord({ x: right, y: bottom })],
    width: minEnclosingRect.width * scale, // 内容宽度
    height: minEnclosingRect.height * scale, // 内容高度
  };
  // 可视区域
  const view = {
    points: [
      { x: 0, y: 0 },
      { x: canvasWidth, y: canvasHeight },
    ],
    width: canvasWidth,
    height: canvasHeight,
  };
  // 计算滚动条
  const computer = ({ contentRange, viewRange, contentDist, viewDist, base }) => {
    const startInner = contentRange[0] >= viewRange[0]; // 起始边是否在可视区域内侧
    const startDist = Math.abs(contentRange[0] - viewRange[0]); // 起始边距离
    const endInner = contentRange[1] <= viewRange[1]; // 结束边是否在可视区域内侧
    const endDist = Math.abs(viewRange[1] - contentRange[1]); // 结束边距离
    // 起始边和结束边都在可视区域内侧 不需要滚动条
    if (startInner && endInner) return { offset: 0, length: 0 };
    let scrollDist = 0; // 可滚动距离
    if (startInner) scrollDist = startDist + contentDist;
    else if (endInner) scrollDist = endDist + contentDist;
    else scrollDist = contentDist;
    const offsetPer = startInner ? 0 : startDist / scrollDist; // 起始偏移量百分比
    const barPer = viewDist / scrollDist; // 滚动条百分比
    return {
      offset: offsetPer * base, // 起始偏移量
      length: barPer * base, // 滚动条长度
      rate: (1 / barPer) * (viewDist / base), // 滚动速率
    };
  };
  // 水平滚动条
  const horizontalScrollbar = computer({
    contentRange: [content.points[0].x, content.points[1].x],
    viewRange: [view.points[0].x, view.points[1].x],
    contentDist: content.width,
    viewDist: view.width,
    base: view.width - padding * 2 - thickness,
  });
  // 垂直滚动条
  const verticalScrollbar = computer({
    contentRange: [content.points[0].y, content.points[1].y],
    viewRange: [view.points[0].y, view.points[1].y],
    contentDist: content.height,
    viewDist: view.height,
    base: view.height - padding * 2 - thickness,
  });
  const horizontal = {
    name: 'horizontal',
    x: padding + horizontalScrollbar.offset,
    y: view.height - thickness - padding,
    width: horizontalScrollbar.length,
    height: thickness,
    hovering: false,
    ...horizontalScrollbar,
    ...{ radius, lineWidth },
  };
  const vertical = {
    name: 'vertical',
    x: view.width - thickness - padding,
    y: padding + verticalScrollbar.offset,
    width: thickness,
    height: verticalScrollbar.length,
    hovering: false,
    ...verticalScrollbar,
    ...{ radius, lineWidth },
  };
  // hover 判断
  ctx.lineWidth = padding * 2;
  const horizontalPath = new Path2D();
  horizontalPath.rect(horizontal.x, horizontal.y, horizontal.width, horizontal.height);
  horizontal.hovering =
    ctx.isPointInPath(horizontalPath, offsetCoord.x, offsetCoord.y) ||
    ctx.isPointInStroke(horizontalPath, offsetCoord.x, offsetCoord.y);
  const verticalPath = new Path2D();
  verticalPath.rect(vertical.x, vertical.y, vertical.width, vertical.height);
  vertical.hovering =
    ctx.isPointInPath(verticalPath, offsetCoord.x, offsetCoord.y) ||
    ctx.isPointInStroke(verticalPath, offsetCoord.x, offsetCoord.y);
  ctx.lineWidth = 1;
  return { horizontal, vertical };
}
